﻿using System;
using System.Collections.Generic;
using System.Data.Services.Providers;
using umbraco.DataServices.Linq;
using System.Linq;

namespace umbraco.DataServices
{
    public class ServiceMetadata : IDataServiceMetadataProvider
    {
        private readonly Dictionary<string, ResourceSet> _resourceSets;
        private readonly Dictionary<string, ResourceType> _resourceTypes;
        private readonly Dictionary<string, ResourceAssociationSet> _resourceAssociationSets;
        private readonly string _containerName;
        private readonly string _namespaceName;
        private readonly IDocumentTypeSignatureCollection _signatureCollection;

        /// <summary>Creates new empty metadata definition.</summary>
        /// <param name="containerName">Name of the container to report.</param>
        /// <param name="namespaceName">Namespace name.</param>
        /// <param name="signatures">Umbraco Document Type Signature Tree</param>
        public ServiceMetadata(string containerName, string namespaceName, IDocumentTypeSignatureCollection signatures) {
            if (string.IsNullOrEmpty(containerName))
                throw new ArgumentNullException("containerName");
            if (string.IsNullOrEmpty(namespaceName))
                throw new ArgumentNullException("namespaceName");
            if (signatures == null)
                throw new ArgumentNullException("signatures");

            _resourceSets = new Dictionary<string, ResourceSet>();
            _resourceTypes = new Dictionary<string, ResourceType>();
            _resourceAssociationSets = new Dictionary<string, ResourceAssociationSet>();
            _signatureCollection = signatures;
            _containerName = containerName;
            _namespaceName = namespaceName;
            BuildMetadata();
        }

        public void BuildMetadata() {

            //Setup Base Resource Type
            var baseNodeResource = new ResourceType(typeof(UmbracoNode), ResourceTypeKind.EntityType, null, _namespaceName, "Node", false);
            baseNodeResource.CanReflectOnInstanceType = false;
            baseNodeResource.AddKeyProperty("Id");
            baseNodeResource.AddPrimitiveProperty("ParentId");
            baseNodeResource.AddPrimitiveProperty("Level");
            baseNodeResource.AddPrimitiveProperty("WriterId");
            baseNodeResource.AddPrimitiveProperty("CreatorId");
            baseNodeResource.AddPrimitiveProperty("TemplateId");
            baseNodeResource.AddPrimitiveProperty("SortOrder");
            baseNodeResource.AddPrimitiveProperty("CreateDate");
            baseNodeResource.AddPrimitiveProperty("UpdateDate");
            baseNodeResource.AddPrimitiveProperty("Title");
            baseNodeResource.AddPrimitiveProperty("UrlName");
            baseNodeResource.AddPrimitiveProperty("WriterName");
            baseNodeResource.AddPrimitiveProperty("CreatorName");
            baseNodeResource.AddPrimitiveProperty("DocumentType");
            baseNodeResource.AddPrimitiveProperty("DocumentTypeId");
            _resourceTypes.Add("Node", baseNodeResource);

            var baseNodeResourceSet = new ResourceSet(baseNodeResource.Name, baseNodeResource);
            _resourceSets.Add(baseNodeResourceSet.Name, baseNodeResourceSet);

            //Parent Reference
            var parentProperty = baseNodeResource.AddRelationshipReference("Parent", baseNodeResource);
            var parentResourceTypeEnd = new ResourceAssociationSetEnd(baseNodeResourceSet, baseNodeResource, parentProperty);
            var parentResourceTypeEndFinish = new ResourceAssociationSetEnd(baseNodeResourceSet, baseNodeResource, null);
            var parentResourceTypes = new ResourceAssociationSet("Parent", parentResourceTypeEnd, parentResourceTypeEndFinish);
            _resourceAssociationSets.Add(parentResourceTypes.Name, parentResourceTypes);

            //Descendants Relationship
            var descemdentProperty = baseNodeResource.AddRelationshipSet("Descendants", baseNodeResource);
            var descemdentResourceTypeEnd = new ResourceAssociationSetEnd(baseNodeResourceSet, baseNodeResource, descemdentProperty);
            var descemdentResourceTypeEndFinish = new ResourceAssociationSetEnd(baseNodeResourceSet, baseNodeResource, null);
            var descemdentResourceTypes = new ResourceAssociationSet("Descendants", descemdentResourceTypeEnd, descemdentResourceTypeEndFinish);
            _resourceAssociationSets.Add(descemdentResourceTypes.Name, descemdentResourceTypes);

            //Ancestors Relationship
            var ancestorsProperty = baseNodeResource.AddRelationshipSet("Ancestors", baseNodeResource);
            var ancestorsResourceTypeEnd = new ResourceAssociationSetEnd(baseNodeResourceSet, baseNodeResource, ancestorsProperty);
            var ancestorsResourceTypeEndFinish = new ResourceAssociationSetEnd(baseNodeResourceSet, baseNodeResource, null);
            var ancestorsResourceTypes = new ResourceAssociationSet("Ancestors", ancestorsResourceTypeEnd, ancestorsResourceTypeEndFinish);
            _resourceAssociationSets.Add(ancestorsResourceTypes.Name, ancestorsResourceTypes);

            //Sibblings Relationship
            var sibblingsProperty = baseNodeResource.AddRelationshipSet("Sibblings", baseNodeResource);
            var sibblingsResourceTypeEnd = new ResourceAssociationSetEnd(baseNodeResourceSet, baseNodeResource, sibblingsProperty);
            var sibblingsResourceTypeEndFinish = new ResourceAssociationSetEnd(baseNodeResourceSet, baseNodeResource, null);
            var sibblingsResourceTypes = new ResourceAssociationSet("Sibblings", sibblingsResourceTypeEnd, sibblingsResourceTypeEndFinish);
            _resourceAssociationSets.Add(sibblingsResourceTypes.Name, sibblingsResourceTypes);

            //Children Relationship
            var childrenProperty = baseNodeResource.AddRelationshipSet("Children", baseNodeResource);
            var childrenResourceTypeEnd = new ResourceAssociationSetEnd(baseNodeResourceSet, baseNodeResource, childrenProperty);
            var childrenResourceTypeEndFinish = new ResourceAssociationSetEnd(baseNodeResourceSet, baseNodeResource, null);
            var childrenResourceTypes = new ResourceAssociationSet("Children", childrenResourceTypeEnd, childrenResourceTypeEndFinish);
            _resourceAssociationSets.Add(childrenResourceTypes.Name, childrenResourceTypes);

            //Build Data Tree Of Inheried Document Types
            foreach (var item in _signatureCollection.Where(p => p.BaseDocumentType == null))
                BuildLevel(item, baseNodeResource);

            //Lock All ResourceSets And ResourceTypes
            foreach (var item in _resourceTypes)
                item.Value.SetReadOnly();
            foreach (var item in _resourceSets)
                item.Value.SetReadOnly();
        }

        public void BuildLevel(DocumentTypeSignature documentTypeSignature, ResourceType parentResourceType) {
            var resourceType = new ResourceType(typeof(UmbracoNode), ResourceTypeKind.EntityType, parentResourceType, _namespaceName, documentTypeSignature.Alias, false);
            resourceType.CanReflectOnInstanceType = false;
            foreach (var property in documentTypeSignature.Properties)
                resourceType.AddDynamicProperty(property.Alias, property.Type);
            resourceType.SetReadOnly();
            _resourceTypes.Add(resourceType.FullName, resourceType);

            var resourceSet = new ResourceSet(documentTypeSignature.Alias, resourceType);
            resourceSet.SetReadOnly();
            _resourceSets.Add(resourceSet.Name, resourceSet);

            var inheritedDocumentTypes = _signatureCollection.GetChildDocumentTypes(documentTypeSignature);
            foreach (var item in inheritedDocumentTypes)
                BuildLevel(item, resourceType);
        }

        public bool TryResolveResourceSet(string name, out ResourceSet resourceSet) {
            return _resourceSets.TryGetValue(name, out resourceSet);
        }

        public ResourceAssociationSet GetResourceAssociationSet(ResourceSet resourceSet, ResourceType resourceType, ResourceProperty resourceProperty) {
            return _resourceAssociationSets.Select(p => p.Value).FirstOrDefault(p => p.End1.ResourceSet == resourceSet && p.End1.ResourceType == resourceType && p.End1.ResourceProperty == resourceProperty);
        }

        public bool TryResolveResourceType(string name, out ResourceType resourceType) {
            return _resourceTypes.TryGetValue(name, out resourceType);
        }

        public IEnumerable<ResourceType> GetDerivedTypes(ResourceType resourceType) {
            var name = resourceType.Name;
            var descendentTypes = _signatureCollection.GetDescendantDocumentTypes(name).Select(p => p.Alias);
            return _resourceTypes.Where(p => descendentTypes.Contains(p.Key)).Select(p => p.Value);
        }

        public bool HasDerivedTypes(ResourceType resourceType) {
            var items = GetDerivedTypes(resourceType);
            return items.Any();
        }

        public bool TryResolveServiceOperation(string name, out ServiceOperation serviceOperation) {
            serviceOperation = null;
            return false;
        }

        public string ContainerNamespace {
            get { return _namespaceName; }
        }

        public string ContainerName {
            get { return _containerName; }
        }

        public IEnumerable<ResourceSet> ResourceSets {
            get { return _resourceSets.Values; }
        }

        public IEnumerable<ResourceType> Types {
            get { return _resourceTypes.Values; }
        }

        public IEnumerable<ServiceOperation> ServiceOperations {
            get { return new ServiceOperation[0]; }
        }
    }
}
